
import Singleton from "../Common/Singleton";

class PathNode {
    //位置坐标
    public pos: cc.Vec2 = new cc.Vec2(0, 0);
    //当前位置离开始位置的距离G
    public curToBeginDis: number = 0;
    //当前位置离目标的最小距离H
    public curToEndDis: number = 0;
    //F值 = G + H
    public f: number = 0;
    //是否可移动
    public canMove: boolean = false;
    //父节点
    public parentNode: PathNode = null;
    //是否在open队列中
    public isInOpen: boolean = false;
    //是否在close队列中
    public isInClose: boolean = false;
};


//A星算法
export class AStar extends Singleton<AStar>{
    //地图层
    public tiledLayer: cc.TiledLayer = null;
    //open队列
    public openQueue: Array<PathNode> = [];
    //close队列
    public closeQueue: Array<PathNode> = [];

    private reset():void{
        this.openQueue = [];
        this.closeQueue = [];
    }

    public findShortPath(begin:cc.Vec2, end:cc.Vec2): Array<cc.Vec2>{

        //初始化首/尾节点
        let bNode = new PathNode();
        bNode.pos = begin;
        bNode.canMove = this.checkCanMove(begin);
        this.insertOpenNode(bNode);

        let eNode = new PathNode();
        eNode.pos = end;
        eNode.canMove = this.checkCanMove(end);

        //是否查找完成
        let isFinished = false;
        //当前节点
        let curNode: PathNode = null;

        while(this.openQueue.length && !isFinished){
            //获取一个F值最小的节点设置为当前节点
            curNode = this.popOpenNode();
            //将当前节点压入close队列中
            this.insertCloseNode(curNode);
            //获取当前节点相邻的且合法的节点(没有越界,不是Wall,不在openQueue中)
            let adjacentsNode: Array<PathNode> = [];
            this.getAdjacentNode(curNode, eNode, adjacentsNode);

            for(let i = 0; i < adjacentsNode.length; ++i){
                this.calcGHFofNode(adjacentsNode[i], bNode, eNode);
                adjacentsNode[i].parentNode = curNode;
                if(adjacentsNode[i].pos.equals(eNode.pos)){
                    eNode.parentNode = curNode;
                    isFinished = true;
                    break;
                }
                this.insertOpenNode(adjacentsNode[i]);
            }
        }

        let shortPath: Array<cc.Vec2> = new Array<cc.Vec2>();
        if(isFinished){
            for(let node: PathNode = eNode; node != null; node = node.parentNode){
                shortPath.push(node.pos);
            }
        }

        this.reset();

        return shortPath;
    }

    //获取当前节点相邻的且合法的节点(没有越界,不是Wall,不在openQueue中)
    private getAdjacentNode(curNode: PathNode, eNode: PathNode, out: Array<PathNode>):void{
        if(curNode){
            let points: Array<cc.Vec2> = [];
            points.push(curNode.pos.add(cc.v2(0, -1)));
            points.push(curNode.pos.add(cc.v2(-1, 0)));
            points.push(curNode.pos.add(cc.v2(0, 1)));
            points.push(curNode.pos.add(cc.v2(1, 0)));

            points.push(curNode.pos.add(cc.v2(-1, 1)));
            points.push(curNode.pos.add(cc.v2(1, 1)));
            points.push(curNode.pos.add(cc.v2(-1, -1)));
            points.push(curNode.pos.add(cc.v2(1, -1)));


            points.forEach((val:cc.Vec2, key: number)=>{
                if(this.checkVecValid(val)){
                    let canMove = this.checkCanMove(val);
                    if((canMove || eNode.pos.equals(val)) && !this.isInOpen(val) && !this.isInClose(val)){
                        let node = new PathNode();
                        node.pos = val;
                        node.canMove = canMove;
                        out.push(node);
                    }
                }
            });
        }
    }

    //是否在open中
    private isInOpen(v: cc.Vec2):boolean{
        for(let i = 0; i < this.openQueue.length; ++i){
            if(this.openQueue[i].pos.equals(v)){
                return true;
            }
        }
        return false;
    }

    //是否在close中
    private isInClose(v: cc.Vec2):boolean{
        for(let i = 0; i < this.closeQueue.length; ++i){
            if(this.closeQueue[i].pos.equals(v)){
                return true;
            }
        }
        return false;
    }

    //检查节点是否有效
    private checkVecValid(v: cc.Vec2):boolean{
        let size: cc.Size = this.tiledLayer.getLayerSize();
        if(v.x >= 0 && v.x < size.width && v.y >= 0 && v.y < size.height){
            return true;
        }
        return false;
    }

    //计算f值
    private calcGHFofNode(tNode: PathNode, bNode: PathNode, eNode: PathNode){
        if(tNode && bNode && eNode){
            let tmp = tNode.pos.sub(bNode.pos);
            let gValue:number = tmp.mag();

            tmp = tNode.pos.sub(eNode.pos);
            let hValue:number = tmp.mag();

            tNode.curToBeginDis = gValue;
            tNode.curToEndDis = hValue;
            tNode.f = gValue + hValue;
        }
    }

    //检查节点是否可移动
    private checkCanMove(v: cc.Vec2):boolean{
        let tile = this.tiledLayer.getTileGIDAt(v.x, v.y);
        return tile == 0 ? true : false;
    }
    private insertOpenNode(node: PathNode):void{
        node.isInOpen = true;
        this.openQueue.push(node);
        this.openQueue.sort((a: PathNode, b: PathNode) => b.f - a.f);
    }
    private popOpenNode():PathNode{
        let node = this.openQueue.pop();
        node.isInOpen = false;
        return node;
    }
    private insertCloseNode(node: PathNode):void{
        node.isInClose = true;
        this.closeQueue.push(node);
    }
}

namespace gAStar{
    export let instance = AStar.instance.bind(AStar, AStar)
}
export default gAStar;